/*
 *  Copyright 2012 Iurii Dunko, Iana Potochniak.
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 * 
 *       http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *  under the License.
 */
package org.opu.yyminijava.execution;

import java.io.File;
import java.io.IOException;
import org.apache.commons.io.FilenameUtils;
import org.opu.yyminijava.codegeneration.ClassRepository;
import org.opu.yyminijava.codegeneration.ICode;
import org.opu.yyminijava.codegeneration.Instruction;
import org.opu.yyminijava.codegeneration.Method;
import org.opu.yyminijava.util.CodeExecuteException;
import org.opu.yyminijava.util.Stack;
import static org.opu.yyminijava.execution.ExecutionModule.MAIN;

/**
 *
 * @author Iurii Dunko
 * @author Iana Potochniak
 */
public class StackMachine {

    private ClassRepository repository;
    private Stack<Integer> stack = new Stack<Integer>();
    private Stack<ExecutionModule> methods = new Stack<ExecutionModule>();
    
    private boolean debug;
    
    public StackMachine(File file, boolean debug) throws IOException {
        repository = new ClassRepository(file);
        ExecutionModule.reset(); // each file each main method .
        String main = FilenameUtils.removeExtension(file.getName()) + '.' + MAIN;
        methods.push(new ExecutionModule(repository.getMethod(main)));
        this.debug = debug;
    }

    public void startExecution() {
        while (goNext()) {
            Instruction next = methods.peek().next();
            ICode opCode = next.getOperationCode();
            Object arg = next.getArgument();
            debug(currentMethod().getName() + ":" + next + " " + stack);
            switch (opCode) {
                // work with data stack
                case ILOAD:
                    stack.push(currentMethod().getVariable(atoi(arg)));
                    break;
                case ICONST:
                    stack.push(atoi(arg));
                    break;
                case ISTORE:
                    currentMethod().setVariableValue(atoi(arg), stack.pop());
                    break;
                // math operation
                case IADD:
                    stack.push(stack.pop() + stack.pop());
                    break;
                case IMUL:
                    stack.push(stack.pop() * stack.pop());
                    break;
                case IDIV:
                    stack.push(div(stack.pop(), stack.pop()));
                    break;
                case ISUB:
                    stack.push(sub(stack.pop(), stack.pop()));
                    break;
                case ILT://Push 1 if v2 < v1 else Push 0
                    stack.push(stack.pop() > stack.pop()? 1 : 0);
                    break;
                case IGT://Push 1 if v2 > v1 else Push 0
                    stack.push(stack.pop() < stack.pop()? 1 : 0);
                    break;
                case IAND://Push 0 if v1 ∗ v2 = 0 else Push 1
                    stack.push(stack.pop() * stack.pop() == 0 ? 0 : 1);
                    break;
                case IOR://Push 0 if v1 + v2 = 0 else Push 1
                    stack.push(stack.pop() + stack.pop() == 0 ? 0 : 1);
                    break;
                case IEQUAL://Push 1 if v1 == v2 else Push 0
                    stack.push(stack.pop() == stack.pop() ? 1 : 0);
                    break;
                case INOT_EQUAL://Push 1 if v1 != v2 else Push 0
                    stack.push(stack.pop() != stack.pop() ? 1 : 0);
                    break;
                case INOT:// Push 1 if v = 0 else Push 0
                    stack.push(stack.pop() == 0 ? 1 : 0);
                    break;
                // operators
                case GOTO:
                    methods.peek().setIndex(atoi(arg));
                    break;
                case IF_FALSE:
                    if (stack.pop() == 0){
                        methods.peek().setIndex(atoi(arg));
                    }
                    break;
                case INVOKEVIRTUAL:
                    methods.push(new ExecutionModule(repository.getMethod((String)arg)));
                    debug("go in: " + methods);
                    break;
                case RETURN:
                    debug("go out: " + methods);
                    methods.pop();
                    break;
                case PRINT:
                    System.out.print(stack.pop());
                    break;
                case PRINTLN:
                    System.out.println(stack.pop());
                    break;
                case STOP:
                    return;
                default:
                    throw new CodeExecuteException("Unsuported op: " + opCode);
            }
        }
    }

    private boolean goNext(){
        if (!methods.isEmpty()){
            if(methods.peek().hasNext()){
                return true;
            } else {
                debug("go out: " + methods);
                methods.pop();
                return goNext();
            }
        } else {
            return false;
        }
    }

    public int div(int v1, int v2){
        return v2 / v1;
    }

    public int sub(int v1, int v2){
        return v2 - v1;
    }

    private Method currentMethod() {
        return methods.peek().getMethod();
    }

    private int atoi(Object o){
        if (o == null){
            throw new NullPointerException();
        }
        return (Integer) o;
    }

    private void debug(String msg){
        if (debug) {
            System.out.println(">" + msg);
        }
    }
}
